﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PostSharp.CodeModel;
using PostSharp.CodeModel.Helpers;
using PostSharp.CodeWeaver;
using PostSharp.Collections;
using PostSharp.Extensibility;
using PostSharp.Extensibility.Tasks;

namespace PropFu
{
    class NotifyPropertyChangedTask : Task, IAdviceProvider
    {
        public void ProvideAdvices(Weaver codeWeaver)
        {
            CustomAttributeDictionaryTask customAttributeDictionary = CustomAttributeDictionaryTask.GetTask(this.Project);

            IEnumerator<ICustomAttributeInstance> customAttributeEnumerator =
                customAttributeDictionary.GetCustomAttributesEnumerator(typeof(NotifyPropertyChangedAttribute), false);

            while (customAttributeEnumerator.MoveNext())
            {
                PropertyDeclaration propertyDeclaration = customAttributeEnumerator.Current.TargetElement as PropertyDeclaration;

                if (propertyDeclaration == null || propertyDeclaration.Visibility == Visibility.Private)
                    continue;

                MethodDefDeclaration getMethodDef;
                MethodDefDeclaration setMethodDef;

                GetPropertyAccessorDefinitions(propertyDeclaration, out getMethodDef, out setMethodDef);
                if (getMethodDef == null || setMethodDef == null)
                {
                    continue;
                }

                NotifyPropertyChangedAdvice advice = new NotifyPropertyChangedAdvice(
                    getMethodDef,
                    setMethodDef);

                codeWeaver.AddMethodLevelAdvice(advice,
                                                 new Singleton<MethodDefDeclaration>(setMethodDef),
                                                 JoinPointKinds.BeforeMethodBody |
                                                 JoinPointKinds.AfterMethodBodySuccess,
                                                 null);
            }
        }

        private static void GetPropertyAccessorDefinitions(PropertyDeclaration propertyDeclaration, out MethodDefDeclaration getMethodDef, out MethodDefDeclaration setMethodDef)
        {
            getMethodDef = null;
            setMethodDef = null;
            foreach (MethodSemanticDeclaration methodSemanticDef in propertyDeclaration.Members)
            {
                MethodDefDeclaration methodDef = methodSemanticDef.Method;

                if (methodDef.Name.StartsWith("get_"))
                {
                    getMethodDef = methodDef;
                }
                else if (methodDef.Name.StartsWith("set_"))
                {
                    setMethodDef = methodDef;
                }
                else
                {
                    throw new InvalidOperationException("Found a NotifyPropertyChanged attribute on something other than a property");
                }
            }
        }
    }
}
